home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue68 / sync / GpFileSyncMonitors.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  2001-02-27  |  23.1 KB  |  740 lines

  1. {:Monitoring components for some file system-based synchronisation primitives.
  2.   (c) 2001 Primoz Gabrijelcic
  3.  
  4.   @author Primoz Gabrijelcic
  5.   @desc <pre>
  6.  
  7.   Free for personal and commercial use.
  8.   Tested with Delphi 5. Should work with Delphi 4 but not with older versions.
  9.  
  10.   Author           : Primoz Gabrijelcic
  11.   Creation date    : 2001-01-06
  12.   Last modification: 2001-02-27
  13.   Version          : 1.0
  14.  
  15.   </pre>}{
  16.  
  17.   History:
  18.   1.0: 2001-02-27
  19.     - First release version.
  20.     
  21.   0.1: 2001-01-06
  22.     - First alpha version.
  23. }
  24.  
  25. unit GpFileSyncMonitors;
  26.  
  27. interface
  28.  
  29. uses
  30.   Windows,
  31.   Messages,
  32.   Classes,
  33.   GpFileSync;
  34.  
  35. const
  36.   CShortDelay =  250; // ms
  37.   CInterval   = 5000; // ms
  38.  
  39. type
  40.   {:Monitors a mutex and reports all changes.
  41.   }
  42.   TGpFileMutexMonitor = class(TComponent)
  43.   private
  44.     FAcquired       : boolean;
  45.     FActive         : boolean;
  46.     FDeleteOnRelease: boolean;
  47.     FFileMutex      : TGpFileMutex;
  48.     FHandle         : HWND;
  49.     FInterval       : integer;
  50.     FMonitorThread  : TThread;
  51.     FMutexFile      : string;
  52.     FOnAcquired     : TNotifyEvent;
  53.     FOnReleased     : TNotifyEvent;
  54.     FShortDelay     : integer;
  55.     FStreamedActive : boolean;
  56.   protected
  57.     procedure DoAcquired; virtual;
  58.     procedure DoReleased; virtual;
  59.     procedure Loaded; override;
  60.     procedure SetActive(const Value: boolean); virtual;
  61.     procedure StartMonitorThread(reportFirst: boolean); virtual;
  62.     procedure StopMonitorThread; virtual;
  63.     procedure WndProc(var MsgRec: TMessage); virtual;
  64.   public
  65.     constructor Create(AOwner: TComponent); override;
  66.     destructor  Destroy; override;
  67.     function  Acquire(timeout: DWORD): boolean; virtual;
  68.     function  Acquired: boolean; virtual;
  69.     function  IsFree(timeout: DWORD): boolean;
  70.     procedure Release; virtual;
  71.   published
  72.     //:Specifies if monitoring is active.
  73.     property Active: boolean read FActive write SetActive;
  74.     //:Timeout (in ms) used when checking if mutex is acquired. Passed to TGpFileMutex.Acquired.
  75.     property CheckTimeout: integer read FShortDelay write FShortDelay default CShortDelay;
  76.     //:Specifies if synchronisation file should be deleted on release. Passed to TGpFileMutex.Create.
  77.     property DeleteOnRelease: boolean read FDeleteOnRelease write FDeleteOnRelease;
  78.     //:Interval (in ms) between two checks.
  79.     property Interval: integer read FInterval write FInterval default CInterval;
  80.     //:True if mutex is acquired by some other owner (not by self).
  81.     property IsAcquired: boolean read FAcquired;
  82.     //:Mutex synchronisation file.
  83.     property MutexFile: string read FMutexFile write FMutexFile;
  84.     {:Triggered when mutex is acquired. Due to the design choices this event
  85.       doesn't trigger when mutex is acquired from within this component
  86.       (self.Acquire). For the same reason this event may in some conditions be
  87.       triggered even if previous mutex status was Acquired, too.
  88.     }
  89.     property OnAcquired: TNotifyEvent read FOnAcquired write FOnAcquired;
  90.     {:Triggered when mutex is released. Due to the design choices this event
  91.       doesn't trigger when mutex is released from within this component
  92.       (self.Release). For the same reason this event may in some conditions be
  93.       triggered even if previous mutex status was Released, too.
  94.     }
  95.     property OnReleased: TNotifyEvent read FOnReleased write FOnReleased;
  96.   end; { TGpFileMutexMonitor }
  97.  
  98.   {:Monitors a group and reports all changes.
  99.   }
  100.   TGpFileGroupMonitor = class(TComponent)
  101.   private
  102.     FActive         : boolean;
  103.     FDeleteOnRelease: boolean;
  104.     FEmpty          : boolean;
  105.     FFileGroup      : TGpFileGroup;
  106.     FGroupFile      : string;
  107.     FHandle         : HWND;
  108.     FInterval       : integer;
  109.     FMonitorThread  : TThread;
  110.     FOnEmpty        : TNotifyEvent;
  111.     FOnNotEmpty     : TNotifyEvent;
  112.     FShortDelay     : integer;
  113.     FStreamedActive : boolean;
  114.   protected
  115.     procedure DoEmpty; virtual;
  116.     procedure DoNotEmpty; virtual;
  117.     procedure Loaded; override;
  118.     procedure SetActive(const Value: boolean); virtual;
  119.     procedure StartMonitorThread(reportFirst: boolean); virtual;
  120.     procedure StopMonitorThread; virtual;
  121.     procedure WndProc(var MsgRec: TMessage); virtual;
  122.   public
  123.     constructor Create(AOwner: TComponent); override;
  124.     destructor  Destroy; override;
  125.     function    IsMember: boolean;
  126.     function    Join(timeout: DWORD; var isFirstMember: boolean): boolean; overload;
  127.     function    Join(timeout: DWORD): boolean; overload;
  128.     function    Leave(timeout: DWORD; var wasLastMember: boolean): boolean; overload;
  129.     function    Leave(timeout: DWORD): boolean; overload;
  130.   published
  131.     //:Specifies if monitoring is active.
  132.     property Active: boolean read FActive write SetActive;
  133.     //:Timeout (in ms) used when checking if group is empty. Passed to TGpFileGroup.Join.
  134.     property CheckTimeout: integer read FShortDelay write FShortDelay default CShortDelay;
  135.     //:Specifies if synchronisation file should be deleted on release. Passed to TGpFileGroup.Create.
  136.     property DeleteOnRelease: boolean read FDeleteOnRelease write FDeleteOnRelease;
  137.     //:Interval (in ms) between two checks.
  138.     property Interval: integer read FInterval write FInterval default CInterval;
  139.     //:True if group is empty.
  140.     property IsEmpty: boolean read FEmpty;
  141.     //:Group synchronisation file.
  142.     property GroupFile: string read FGroupFile write FGroupFile;
  143.     {:Triggered when group becomes empty. Due to the design choices this event
  144.       may in some conditions be triggered even if previous group status was
  145.       Empty, too.
  146.     }
  147.     property OnEmpty: TNotifyEvent read FOnEmpty write FOnEmpty;
  148.     {:Triggered when group becomes nonempty. Due to the design choices this
  149.       event may in some conditions be triggered even if previous group status
  150.       was NotEmpty, too.
  151.     }
  152.     property OnNotEmpty: TNotifyEvent read FOnNotEmpty write FOnNotEmpty;
  153.   end; { TGpFileMutexMonitor }
  154.  
  155. procedure Register;
  156.  
  157. implementation
  158.  
  159. uses
  160.   SysUtils,
  161.   Forms;
  162.  
  163. const
  164.   WM_ACQUIRED = WM_USER;
  165.   WM_RELEASED = WM_USER+1;
  166.   WM_EMPTY    = WM_USER+2;
  167.   WM_NOTEMPTY = WM_USER+3;
  168.  
  169. type
  170.   {:Base monitor thread class - implements TerminateEvent support.
  171.   }
  172.   TGpFileAnyMonitorThread = class(TThread)
  173.   public
  174.     TerminateEvent: THandle;
  175.     constructor Create(CreateSuspended: boolean);
  176.     destructor  Destroy; override;
  177.     procedure   Execute; override;
  178.     procedure   SafeExecute; virtual; abstract;
  179.     procedure   Terminate;
  180.     property    Terminated;
  181.   end; { TGpFileAnyMonitorThread }
  182.  
  183.   {:File mutex monitor - monitors mutex and notifies owner of changes.
  184.   }
  185.   TGpFileMutexMonitorThread = class(TGpFileAnyMonitorThread)
  186.   private
  187.     FCheckInterval: integer;
  188.     FFileMutex    : TGpFileMutex;
  189.     FParentWin    : HWND;
  190.     FShortDelay   : integer;
  191.     FReportFirst  : boolean;
  192.     procedure TriggerStatusChange(isFree: boolean);
  193.   public
  194.     constructor Create(parentWin: HWND; checkInterval: integer;
  195.       mutexFile: string; deleteOnRelease: boolean; shortDelay: integer;
  196.       reportFirst: boolean);
  197.     destructor  Destroy; override;
  198.     procedure SafeExecute; override;
  199.   end; { TGpFileMutexMonitorThread }
  200.  
  201.   {:File group monitor - monitors group and notifies owner of changes.
  202.   }
  203.   TGpFileGroupMonitorThread = class(TGpFileAnyMonitorThread)
  204.   private
  205.     FCheckInterval: integer;
  206.     FFileGroup    : TGpFileGroup;
  207.     FParentWin    : HWND;
  208.     FShortDelay   : integer;
  209.     FReportFirst  : boolean;
  210.     procedure TriggerStatusChange(isEmpty: boolean);
  211.   public
  212.     constructor Create(parentWin: HWND; checkInterval: integer;
  213.       groupFile: string; deleteOnRelease: boolean; shortDelay: integer;
  214.       reportFirst: boolean);
  215.     destructor  Destroy; override;
  216.     procedure SafeExecute; override;
  217.   end; { TGpFileMutexMonitorThread }
  218.  
  219. resourcestring
  220.   SAlreadyJoined = 'Already joined: %s.';
  221.   SNotJoined     = 'Not member: %s.';
  222.  
  223. procedure Register;
  224. begin
  225.   RegisterComponents('Gp',[TGpFileMutexMonitor,TGpFileGroupMonitor]);
  226. end; { Register }
  227.  
  228. { TGpFileAnyMonitorThread }
  229.  
  230. {:Base monitor thread class constructor.
  231. }
  232. constructor TGpFileAnyMonitorThread.Create(CreateSuspended: boolean);
  233. begin
  234.   TerminateEvent := CreateEvent(nil, true, false, nil);
  235.   inherited Create(CreateSuspended);
  236. end; { TGpFileAnyMonitorThread.Create }
  237.  
  238. {:Base monitor thread class destructor.
  239. }
  240. destructor TGpFileAnyMonitorThread.Destroy;
  241. begin
  242.   CloseHandle(TerminateEvent);
  243.   inherited;
  244. end; { TGpFileAnyMonitorThread.Destroy }
  245.  
  246. {:Base monitor thread class Execute method. Calls abstract SafeExecute method
  247.   and makes sure that Terminate is called at the end to set TerminateEvent.
  248. }
  249. procedure TGpFileAnyMonitorThread.Execute;
  250. begin
  251.   try
  252.     SafeExecute;
  253.   finally 
  254.     Terminate; // set Terminated
  255.   end;
  256. end; { TGpFileAnyMonitorThread.Execute }
  257.  
  258. {:Statically overridden (sadly, Terminate is not virtual in TThread) to set
  259.   TerminateEvent.
  260. }
  261. procedure TGpFileAnyMonitorThread.Terminate;
  262. begin
  263.   inherited;
  264.   SetEvent(TerminateEvent);
  265. end; { TGpFileAnyMonitorThread.Terminate }
  266.  
  267. { TGpFileMutexMonitorThread }
  268.  
  269. {:Mutex monitor thread constructor.
  270. }
  271. constructor TGpFileMutexMonitorThread.Create(parentWin: HWND;
  272.   checkInterval: integer; mutexFile: string; deleteOnRelease: boolean;
  273.   shortDelay: integer; reportFirst: boolean);
  274. begin
  275.   FParentWin := parentWin;
  276.   FCheckInterval := checkInterval;
  277.   FShortDelay := shortDelay;
  278.   FReportFirst := reportFirst;
  279.   FFileMutex := TGpFileMutex.Create(mutexFile,deleteOnRelease);
  280.   inherited Create(false);
  281. end; { TGpFileMutexMonitorThread.Create }
  282.  
  283. {:Mutex monitor thread destructor.
  284. }
  285. destructor TGpFileMutexMonitorThread.Destroy;
  286. begin
  287.   //safety precaution - that way code will work correctly if caller forgets to Terminate this thread
  288.   Terminate;
  289.   WaitFor;
  290.   //>
  291.   FreeAndNil(FFileMutex);
  292.   inherited;
  293. end; { TGpFileMutexMonitorThread.Destroy }
  294.  
  295. {:Mutex monitor thread main execute loop. Monitors for mutex changes and reports
  296.   them to the owner.
  297. }
  298. procedure TGpFileMutexMonitorThread.SafeExecute;
  299. var
  300.   newIsFree: boolean;
  301.   oldIsFree: boolean;
  302. begin
  303.   oldIsFree := FFileMutex.IsFree(FShortDelay);
  304.   if FReportFirst or (not oldIsFree) then
  305.     TriggerStatusChange(oldIsFree);
  306.   while WaitForSingleObject(TerminateEvent,FCheckInterval) = WAIT_TIMEOUT do begin
  307.     newIsFree := FFileMutex.IsFree(FShortDelay);
  308.     if newIsFree <> oldIsFree then begin
  309.       TriggerStatusChange(newIsFree);
  310.       oldIsFree := newIsFree;
  311.     end;
  312.   end; //while
  313. end; { TGpFileMutexMonitorThread.SafeExecute }
  314.  
  315. {:Report mutex status change to the owner by posting an user message.
  316. }
  317. procedure TGpFileMutexMonitorThread.TriggerStatusChange(isFree: boolean);
  318. begin
  319.   if isFree then
  320.     PostMessage(FParentWin,WM_RELEASED,0,0)
  321.   else
  322.     PostMessage(FParentWin,WM_ACQUIRED,0,0);
  323. end; { TGpFileMutexMonitorThread.TriggerStatusChange }
  324.  
  325. { TGpFileMutexMonitor }
  326.  
  327. {:Tries to acquire mutex.
  328.   @param   timeout Timeout in milliseconds. 0 and INFINITE are supported.
  329.   @returns true if mutex was acquired.
  330.   @raises  EGpFileSync if sync file cannot be created of if mutex is already
  331.            acquired.
  332. }
  333. function TGpFileMutexMonitor.Acquire(timeout: DWORD): boolean;
  334. begin
  335.   if assigned(FFileMutex) then
  336.     Result := false
  337.   else begin
  338.     StopMonitorThread;
  339.     FFileMutex := TGpFileMutex.Create(FMutexFile,FDeleteOnRelease);
  340.     Result := FFileMutex.Acquire(timeout);
  341.     if not Result then
  342.       Release
  343.     else if FAcquired then // monitoring thread failed to notice that mutex is no longer acquired
  344.       DoReleased;
  345.   end;
  346. end; { TGpFileMutexMonitor.Acquire }
  347.  
  348. {:Checks if mutex is acquired.
  349.   returns true if mutex is currently acquired by self.
  350. }
  351. function TGpFileMutexMonitor.Acquired: boolean;
  352. begin
  353.   Result := assigned(FFileMutex);
  354. end; { TGpFileMutexMonitor.Acquired }
  355.  
  356. {:Creates mutex monitor component.
  357.   @param   AOwner Component owner.
  358. }
  359. constructor TGpFileMutexMonitor.Create(AOwner: TComponent);
  360. begin
  361.   inherited;
  362.   FInterval := 5000;
  363.   FShortDelay := CShortDelay;
  364.   FHandle := AllocateHWnd(WndProc);
  365. end; { TGpFileMutexMonitor.Create }
  366.  
  367. {:Destroys mutex monitor component.
  368. }
  369. destructor TGpFileMutexMonitor.Destroy;
  370. begin
  371.   StopMonitorThread;
  372.   DeallocateHWnd(FHandle);
  373.   FreeAndNil(FFileMutex);
  374.   inherited;
  375. end; { TGpFileMutexMonitor.Destroy }
  376.  
  377. {:Called when mutex is acquired (by another owner). Sets internal flag and
  378.   triggers OnAcquired event.
  379. }
  380. procedure TGpFileMutexMonitor.DoAcquired;
  381. begin
  382.   FAcquired := true;
  383.   if assigned(FOnAcquired) then
  384.     FOnAcquired(self);
  385. end; { TGpFileMutexMonitor.DoAcquired }
  386.  
  387. {:Called when mutex is released (by another owner). Sets internal flag and
  388.   triggers OnAcquired event.
  389. }
  390. procedure TGpFileMutexMonitor.DoReleased;
  391. begin
  392.   FAcquired := false;
  393.   if assigned(FOnReleased) then
  394.     FOnReleased(self);
  395. end; { TGpFileMutexMonitor.DoReleased }
  396.  
  397. {:Checks if mutex can be acquired but does not acquire it.
  398.   @returns true if mutex can be acquired.
  399.   @raises  EGpFileSync if sync file cannot be created.
  400. }
  401. function TGpFileMutexMonitor.IsFree(timeout: DWORD): boolean;
  402. begin
  403.   if Acquire(timeout) then begin
  404.     Release;
  405.     Result := true;
  406.   end
  407.   else
  408.     Result := false;
  409. end; { TGpFileMutexMonitor.IsFree }
  410.  
  411. {:Called when component is fully loaded. Sets delay-loaded Active property.
  412. }
  413. procedure TGpFileMutexMonitor.Loaded;
  414. begin
  415.   inherited;
  416.   if FStreamedActive then
  417.     Active := true;
  418. end; { TGpFileMutexMonitor.Loaded }
  419.  
  420. {:Releases mutex. Does nothing if mutex is not acquired.
  421. }
  422. procedure TGpFileMutexMonitor.Release;
  423. begin
  424.   FreeAndNil(FFileMutex);
  425.   if FActive then
  426.     StartMonitorThread(false);
  427. end; { TGpFileMutexMonitor.Release }
  428.  
  429. {:Sets Active property. If called during component loading, stores new value to
  430.   be set later when component is fully loaded. Otherwise, starts or stops
  431.   monitoring thread if required.
  432.   @param   Value New value of Active property.
  433. }
  434. procedure TGpFileMutexMonitor.SetActive(const Value: boolean);
  435. begin
  436.   if (csReading in ComponentState) then
  437.     FStreamedActive := Value
  438.   else begin
  439.     if FActive <> Value then begin
  440.       FActive := Value;
  441.       // if FFileMutex is assigned, thread is already stopped and should not be
  442.       // restarted - it will be restarted in .Release
  443.       if not assigned(FFileMutex) then begin
  444.         if FActive then
  445.           StartMonitorThread(true)
  446.         else
  447.           StopMonitorThread;
  448.       end;
  449.     end;
  450.   end;
  451. end; { TGpFileMutexMonitor.SetActive }
  452.  
  453. {:Starts monitor thread (if not already started).
  454. }
  455. procedure TGpFileMutexMonitor.StartMonitorThread(reportFirst: boolean);
  456. begin
  457.   if not assigned(FMonitorThread) then begin
  458.     FMonitorThread := TGpFileMutexMonitorThread.Create(FHandle,FInterval,
  459.       FMutexFile,FDeleteOnRelease,FShortDelay,reportFirst);
  460.   end;
  461. end; { TGpFileMutexMonitor.StartMonitorThread }
  462.  
  463. {:Stops monitor thread (if not already stopped).
  464. }
  465. procedure TGpFileMutexMonitor.StopMonitorThread;
  466. begin
  467.   if assigned(FMonitorThread) then begin
  468.     TGpFileMutexMonitorThread(FMonitorThread).Terminate;
  469.     FMonitorThread.WaitFor;
  470.     FreeAndNil(FMonitorThread);
  471.   end;
  472. end; { TGpFileMutexMonitor.StopMonitorThread }
  473.  
  474. {:Windows message handler. Catches WM_ACQUIRED and WM_RELEASED user messages and
  475.   converts them into events.
  476. }
  477. procedure TGpFileMutexMonitor.WndProc(var MsgRec: TMessage);
  478. begin
  479.   with MsgRec do begin
  480.     if Msg = WM_ACQUIRED then begin
  481.       DoAcquired;
  482.       Result := 0;
  483.     end
  484.     else if Msg = WM_RELEASED then begin
  485.       DoReleased;
  486.       Result := 0;
  487.     end
  488.     else
  489.       Result := DefWindowProc(FHandle,Msg,wParam,lParam);
  490.   end; //with
  491. end; { TGpFileMutexMonitor.WndProc }
  492.  
  493. { TGpFileGroupMonitor }
  494.  
  495. {:Creates group monitor component.
  496.   @param   AOwner Component owner.
  497. }
  498. constructor TGpFileGroupMonitor.Create(AOwner: TComponent);
  499. begin
  500.   inherited;
  501.   FInterval := 5000;
  502.   FShortDelay := CShortDelay;
  503.   FHandle := AllocateHWnd(WndProc);
  504. end; { TGpFileGroupMonitor.Create }
  505.  
  506. {:Destroys group monitor component.
  507. }
  508. destructor TGpFileGroupMonitor.Destroy;
  509. begin
  510.   StopMonitorThread;
  511.   DeallocateHWnd(FHandle);
  512.   FreeAndNil(FFileGroup);
  513.   inherited;
  514. end; { TGpFileGroupMonitor.Destroy }
  515.  
  516. {:Called when group becomes empty. Sets internal flag and triggers OnEmpty
  517.   event.
  518. }
  519. procedure TGpFileGroupMonitor.DoEmpty;
  520. begin
  521.   FEmpty := true;
  522.   if assigned(FOnEmpty) then
  523.     FOnEmpty(self);
  524. end; { TGpFileGroupMonitor.DoEmpty }
  525.  
  526. {:Called when group becomes nonempty. Sets internal flag and triggers OnNotEmpty
  527.   event.
  528. }
  529. procedure TGpFileGroupMonitor.DoNotEmpty;
  530. begin
  531.   FEmpty := false;
  532.   if assigned(FOnNotEmpty) then
  533.     FOnNotEmpty(self);
  534. end; { TGpFileGroupMonitor.DoNotEmpty }
  535.  
  536. function TGpFileGroupMonitor.IsMember: boolean;
  537. begin
  538.   Result := assigned(FFileGroup);
  539. end; { TGpFileGroupMonitor.IsMember }
  540.  
  541. {:Overloaded Join, does not return status.
  542.   @param   timeout Timeout in milliseconds. 0 and INFINITE are supported.
  543.   @returns false on timeout.
  544.   @raises  EGpFileSync if sync file cannot be created or if already joined.
  545. }
  546. function TGpFileGroupMonitor.Join(timeout: DWORD): boolean;
  547. var
  548.   isFirst: boolean;
  549. begin
  550.   Result := Join(timeout,isFirst);
  551. end; { TGpFileGroupMonitor.Join }
  552.  
  553. {:Joins the group.
  554.   @param   timeout       Timeout in milliseconds. 0 and INFINITE are supported.
  555.   @param   isFirstMember (out) Set to true if this was first member of the
  556.                          group. Defined only if function returns true.
  557.   @returns false on timeout.
  558.   @raises  EGpFileSync if sync file cannot be created or if already joined.
  559. }
  560. function TGpFileGroupMonitor.Join(timeout: DWORD;
  561.   var isFirstMember: boolean): boolean;
  562. begin
  563.   if IsMember then
  564.     raise EGpFileSync.CreateFmt(SAlreadyJoined,[FGroupFile])
  565.   else begin
  566.     FFileGroup := TGpFileGroup.Create(FGroupFile,FDeleteOnRelease);
  567.     Result := FFileGroup.Join(timeout,isFirstMember);
  568.     if not Result then
  569.       FreeAndNil(FFileGroup)
  570.     else if isFirstMember then
  571.       DoNotEmpty;
  572.   end;
  573. end; { TGpFileGroupMonitor.Join }
  574.  
  575. {:Leaves the group.
  576.   @param   timeout       Timeout in milliseconds. 0 and INFINITE are supported.
  577.   @param   wasLastMember (out) Set to true if this was last process in the
  578.                          group. Defined only if function returns true.
  579.   @returns false on timeout.
  580.   @raises  EGpFileSync if sync file cannot be deleted and this was last member
  581.            and deleteOnRelease was required. Also raised if not joined.
  582. }
  583. function TGpFileGroupMonitor.Leave(timeout: DWORD;
  584.   var wasLastMember: boolean): boolean;
  585. begin
  586.   if not IsMember then
  587.     raise EGpFileSync.CreateFmt(SNotJoined,[FGroupFile])
  588.   else begin
  589.     Result := FFileGroup.Leave(timeout,wasLastMember);
  590.     if Result then begin
  591.       FreeAndNil(FFileGroup);
  592.       if wasLastMember then
  593.         DoEmpty;
  594.     end;
  595.   end;
  596. end; { TGpFileGroupMonitor.Leave }
  597.  
  598. {:Overloaded Leave, does not return status.
  599.   @param   timeout Timeout in milliseconds. 0 and INFINITE are supported.
  600.   @returns false on timeout.
  601.   @raises  EGpFileSync if sync file cannot be deleted and this was last member
  602.            and deleteOnRelease was required. Also raised if not joined.
  603. }
  604. function TGpFileGroupMonitor.Leave(timeout: DWORD): boolean;
  605. var
  606.   wasLast: boolean;
  607. begin
  608.   Result := Leave(timeout,wasLast);
  609. end; { TGpFileGroupMonitor.Leave }
  610.  
  611. procedure TGpFileGroupMonitor.Loaded;
  612. begin
  613.   inherited;
  614.   if FStreamedActive then
  615.     Active := true;
  616. end; { TGpFileGroupMonitor.Loaded }
  617.  
  618. {:Sets Active property. If called during component loading, stores new value to
  619.   be set later when component is fully loaded. Otherwise, starts or stops
  620.   monitoring thread if required.
  621.   @param   Value New value of Active property.
  622. }
  623. procedure TGpFileGroupMonitor.SetActive(const Value: boolean);
  624. begin
  625.   if (csReading in ComponentState) then
  626.     FStreamedActive := Value
  627.   else begin
  628.     if FActive <> Value then begin
  629.       FActive := Value;
  630.       if FActive then
  631.         StartMonitorThread(true)
  632.       else
  633.         StopMonitorThread;
  634.     end;
  635.   end;
  636. end; { TGpFileGroupMonitor.SetActive }
  637.  
  638. {:Starts monitor thread (if not already started).
  639. }
  640. procedure TGpFileGroupMonitor.StartMonitorThread(reportFirst: boolean);
  641. begin
  642.   if not assigned(FMonitorThread) then begin
  643.     FMonitorThread := TGpFileGroupMonitorThread.Create(FHandle,FInterval,
  644.       FGroupFile,FDeleteOnRelease,FShortDelay,reportFirst);
  645.   end;
  646. end; { TGpFileGroupMonitor.StartMonitorThread }   
  647.  
  648. {:Stops monitor thread (if not already stopped).
  649. }
  650. procedure TGpFileGroupMonitor.StopMonitorThread;
  651. begin
  652.   if assigned(FMonitorThread) then begin
  653.     TGpFileGroupMonitorThread(FMonitorThread).Terminate;
  654.     FMonitorThread.WaitFor;
  655.     FreeAndNil(FMonitorThread);
  656.   end;
  657. end; { TGpFileGroupMonitor.StopMonitorThread }
  658.  
  659. {:Windows message handler. Catches WM_EMPTY and WM_NOTEMPTY user messages and
  660.   converts them into events.
  661. }
  662. procedure TGpFileGroupMonitor.WndProc(var MsgRec: TMessage);
  663. begin
  664.   with MsgRec do begin
  665.     if Msg = WM_EMPTY then begin
  666.       DoEmpty;
  667.       Result := 0;
  668.     end
  669.     else if Msg = WM_NOTEMPTY then begin
  670.       DoNotEmpty;
  671.       Result := 0;
  672.     end
  673.     else
  674.       Result := DefWindowProc(FHandle,Msg,wParam,lParam);
  675.   end; //with
  676. end; { TGpFileGroupMonitor.WndProc }
  677.  
  678. { TGpFileGroupMonitorThread }
  679.  
  680. {:Group monitor thread constructor.
  681. }
  682. constructor TGpFileGroupMonitorThread.Create(parentWin: HWND;
  683.   checkInterval: integer; groupFile: string; deleteOnRelease: boolean;
  684.   shortDelay: integer; reportFirst: boolean);
  685. begin
  686.   FParentWin := parentWin;
  687.   FCheckInterval := checkInterval;
  688.   FShortDelay := shortDelay;
  689.   FReportFirst := reportFirst;
  690.   FFileGroup := TGpFileGroup.Create(groupFile,deleteOnRelease);
  691.   inherited Create(false);
  692. end; { TGpFileGroupMonitorThread.Create }
  693.  
  694. {:Group monitor thread destructor.
  695. }
  696. destructor TGpFileGroupMonitorThread.Destroy;
  697. begin
  698.   //safety precaution - that way code will work correctly if caller forgets to Terminate this thread
  699.   Terminate;
  700.   WaitFor;
  701.   //>
  702.   FreeAndNil(FFileGroup);
  703.   inherited;
  704. end; { TGpFileGroupMonitorThread.Destroy }
  705.  
  706. {:Group monitor thread main execute loop. Monitors for group changes and reports
  707.   them to the owner.
  708. }
  709. procedure TGpFileGroupMonitorThread.SafeExecute;
  710. var
  711.   newIsEmpty: boolean;
  712.   oldIsEmpty: boolean;
  713. begin
  714.   if not FFileGroup.IsEmpty(FShortDelay,oldIsEmpty) then
  715.     oldIsEmpty := false;
  716.   if FReportFirst then
  717.     TriggerStatusChange(oldIsEmpty);
  718.   while WaitForSingleObject(TerminateEvent,FCheckInterval) = WAIT_TIMEOUT do begin
  719.     if not FFileGroup.IsEmpty(FShortDelay,newIsEmpty) then
  720.       newIsEmpty := oldIsEmpty;
  721.     if newIsEmpty <> oldIsEmpty then begin
  722.       TriggerStatusChange(newIsEmpty);
  723.       oldIsEmpty := newIsEmpty;
  724.     end;
  725.   end; //while
  726. end; { TGpFileGroupMonitorThread. }
  727.  
  728. {:Report mutex status change to the owner by posting an user message.
  729. }
  730. procedure TGpFileGroupMonitorThread.TriggerStatusChange(isEmpty: boolean);
  731. begin
  732.   if isEmpty then
  733.     PostMessage(FParentWin,WM_EMPTY,0,0)
  734.   else
  735.     PostMessage(FParentWin,WM_NOTEMPTY,0,0);
  736. end; { TGpFileGroupMonitorThread.TriggerStatusChange }
  737.  
  738. end.
  739.  
  740.